// 日志落地模块:
// 功能: 将格式化完成后的日志消息字符串，输出到指定的位置
// 扩展: 支持同时将日志落地到不同的位置
// 位置分类:
// 标准输出
// 指定文件 (事后进行日志分析)
// 滚动文件 (文件按照时间 / 大小进行滚动切换，比如达到了 1g 然后就换下一个文件。比如每天的日志消息都放在一个文件里，然后设置有一个定时器帮我们定期清理三天以前的日志信息)
// 也可以落地到专门的一个日志服务器 或者数据库（支持扩展）
// 扩展：支持落地方向的扩展
// 用户可以自己编写一个新的落地模块，将日志进行其他方向的落地
// 实现思想:
// 抽象出落地模块类
// 不同落地方向从基类进行派生
// 使用工厂模式进行创建与表示的分离
// 让所有关系的发生都是通过抽象来进行的！这样的好处就是只要有一个新的模块，我只要用我的父类指针指向你的子类对象。以后后期哪个需要进行创建的调整，不用去项目里面找去哪里创建的这个对象然后修改他的源代码，而是只要在工厂里面去找到他的生产过程进行修改就 ok 了！
// 要注意析构也要写成虚函数，因为我们用户扩展的模块可能会需要！要考虑用户
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include <iostream>
#include <fstream>
#include <memory>
#include <assert.h>
#include "util.hpp"
namespace logsys {
// 抽象的日志落地基类
class LogSink {
public:
    using ptr = std::shared_ptr<LogSink>;
    LogSink() {}
    virtual ~LogSink() {}
    // 纯虚函数，用于将日志数据写入对应的位置，由派生类实现具体逻辑
    virtual void log(const char *data, size_t len = 0) = 0; 
};

// 落地方向：标准输出
class StdoutSink : public LogSink {
public:
    // 将日志消息写入到标准输出
    void log(const char *data, size_t len) {
        std::cout.write(data, len);
    }
};

class FileSink : public LogSink {
public:
    // 构造时传入文件名，并打开文件，将操作句柄管理起来
    FileSink(const std::string &pathname) : _pathname(pathname) {
        // 1. 创建日志文件所在的目录
        util::File::createDirectory(util::File::path(pathname));
        // 2. 创建并打开日志文件
        _ofs.open(_pathname, std::ios::binary | std::ios::app);
        assert(_ofs.is_open());
    }
    // 将日志消息写入到文件
    void log(const char *data, size_t len) {
        _ofs.write(data, len);
        assert(_ofs.good());// 判断句柄是否正常
    }
private:
    std::string _pathname;
    std::ofstream _ofs;
};

// 落地方向：滚动文件（以大小进行滚动）
class RollBySizeSink : public LogSink {
public:
    // 构造时传入文件名，并打开文件，将操作句柄管理起来
    RollBySizeSink(const std::string &basename, size_t max_size) : 
        _basename(basename), _max_fsize(max_size), _cur_fsize(0), _name_count(0) {
        std::string pathname = createNewFile();
        // 1. 创建日志文件所在的目录
        util::File::createDirectory(util::File::path(pathname));
        // 2. 创建并打开日志文件
        _ofs.open(pathname, std::ios::binary | std::ios::app);
        assert(_ofs.is_open());
    }
    // 将日志消息写入到文件，写入前判断文件大小，超过了最大大小就要切换文件
    void log(const char *data, size_t len) {
        if (_cur_fsize >= _max_fsize) {
            _ofs.close();// 关闭原来已经打开的文件
            std::string pathname = createNewFile();
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
            _cur_fsize = 0;
        }
        _ofs.write(data, len);
        assert(_ofs.good());
        _cur_fsize += len;
    }
private:
    // 进行大小判断，超过指定大小则创建新文件
    std::string createNewFile() {
        // 获取系统时间，以时间来构造文件名扩展名
        time_t t = util::Date::now();
        struct tm lt;
        localtime_r(&t, &lt);
        std::stringstream filename;
        filename << _basename;
        filename << lt.tm_year + 1900;  // 将时间戳转换为具体年份，year默认从0开始
        filename << lt.tm_mon + 1;    // 月份，mon默认从0开始
        filename << lt.tm_mday;
        filename << lt.tm_hour;
        filename << lt.tm_min;
        filename << lt.tm_sec;
        filename << ".";
        filename << _name_count++;  // 添加一个计数器防止在一秒内产生多个文件
        filename << ".log";
        return filename.str();
    }
private:
    // 通过基础文件名 + 扩展文件名（以时间生成）组成一个实际的当前输出文件名
    size_t _name_count; 
    std::string _basename; // 基础文件名，例如./logs/base ->./logs/base-20020809132332.log
    std::ofstream _ofs;
    size_t _max_fsize; // 记录最大大小，当前文件超过了这个大小就要切换文件
    size_t _cur_fsize; // 记录当前文件已经写入的数据大小
};

class SinkFactory {
public:
    template <typename SinkType, typename...Args>
    static LogSink::ptr create(Args &&...args) {
        // 使用std::make_shared创建一个SinkType类型的智能指针
        return std::make_shared<SinkType>(std::forward<Args>(args)...);
    }
};
}
#endif